/********************************************************************************************************
 * @file    usbaud.c
 *
 * @brief   This is the source file for B85
 *
 * @author  BLE GROUP
 * @date    06,2020
 *
 * @par     Copyright (c) 2020, Telink Semiconductor (Shanghai) Co., Ltd. ("TELINK")
 *
 *          Licensed under the Apache License, Version 2.0 (the "License");
 *          you may not use this file except in compliance with the License.
 *          You may obtain a copy of the License at
 *
 *              http://www.apache.org/licenses/LICENSE-2.0
 *
 *          Unless required by applicable law or agreed to in writing, software
 *          distributed under the License is distributed on an "AS IS" BASIS,
 *          WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *          See the License for the specific language governing permissions and
 *          limitations under the License.
 *
 *******************************************************************************************************/
#include "../../common/config/user_config.h"

#include "drivers.h"

#include "usbaud.h"
#include "../usbstd/usb.h"
#include "../usbstd/AudioClassCommon.h"

/*************************************************
 * g_audio_hid_chg:
 * 		0x00: no event
 * 		0x01: speaker volume change
 * 		0x02: speaker mute change
 * 		0x11: microphone volume change
 * 		0x12: microphone mute change
 *************************************************/

unsigned char usb_audio_mic_cnt=0;
unsigned char usb_audio_speaker_cnt=0;

static speaker_setting_t speaker_setting;
static mic_setting_t mic_setting;

/**
 * @brief      This function servers to set the mic Endpoint mono function.
 * @param[in]  en- 1 enable, 0 disable.
 * @attention  b85 and b85 do not support iso mode.
 * @return     none.
 */
void usbaud_set_audio_mode(int mono_en) {
	if(mono_en==1)
	{
		BM_SET(reg_usb_ep_ctrl(USB_EDP_MIC), FLD_USB_EP_MONO);
	}
	else
	{
		BM_CLR(reg_usb_ep_ctrl(USB_EDP_MIC), FLD_USB_EP_MONO);
	}
}

#if 0
void usbaud_hid_report(char format, char volume) {
	unsigned char sAudioHidReport[] = { 0x01,/* report ID */
	format,/*[0]:play/pause,
	 [1]:scan next track,
	 [2]:scan previous track,
	 [3]:stop,
	 [4]:play,
	 [5]:pause,
	 [6]:fast forward,
	 [7]:rewind,
	 */
	volume /*[0]:volume up
	 [1]:volume down
	 [2]:mute
	 [3:7] 0;
	 */
	};
	WriteEndPoint(EDP_ID_AUDIO_HID, sAudioHidReport, sizeof(sAudioHidReport));
}
#endif


#if(USB_SPEAKER_ENABLE || USB_MIC_ENABLE)	//  use for volume control, mute, next, prev track,  move to mouse hid
int usbaud_hid_report(u8 cmd, u8 vol){
	if (usbhw_is_ep_busy(USB_EDP_AUDIO_IN))
		return 0;
	reg_usb_ep_ptr(USB_EDP_AUDIO_IN) = 0;
	
	// please refer to keyboard_report_desc
	reg_usb_ep_dat(USB_EDP_AUDIO_IN) = USB_HID_AUDIO;
	reg_usb_ep_dat(USB_EDP_AUDIO_IN) = cmd;
	reg_usb_ep_dat(USB_EDP_AUDIO_IN) = vol;
	
	reg_usb_ep_ctrl(USB_EDP_AUDIO_IN) = FLD_EP_DAT_ACK;		// ACK
	return 1;
}
#endif

#if 0
u8 usbaud_handle_report(u8 c) {
	if (USB_REPORT_NO_EVENT == c) {
		return USB_REPORT_NO_EVENT;
	}
    assert(USB_EDP_AUDIO < 8);
	if(reg_usb_ep_ctrl(USB_EDP_AUDIO) & FLD_USB_EP_BUSY)
		return c;

	if(USB_REPORT_RELEASE == c){
		usbaud_hid_report(0, 0);
		return USB_REPORT_NO_EVENT;
	}else{
		usbaud_hid_report((c < 0x10) ? (1 << c) : 0
			,(c < 0x10) ? 0 : (1 << (c & 0x0f)));
		return USB_REPORT_RELEASE;
	}
}
#endif

static u16 usbaud_cal_speaker_step(s16 vol){
	if(vol < SPEAKER_VOL_MIN) 
		return 0;
	return (vol - SPEAKER_VOL_MIN) / SPEAKER_VOL_RES;
}

static u16 usbaud_cal_mic_step(s16 vol){
	if(vol < MIC_VOL_MIN)
		return 0;
	return (vol - MIC_VOL_MIN) / MIC_VOL_RES;
}

void usbaud_set_speaker_vol(s16 vol){
	speaker_setting.vol_cur = vol;
	speaker_setting.vol_step = usbaud_cal_speaker_step(vol);
}
	
void usbaud_set_mic_vol(s16 vol){
	mic_setting.vol_cur = vol;
	mic_setting.vol_step = usbaud_cal_mic_step(vol);
}

usb_audio_status_t g_usb_audio_status;

u8 usbaud_speaker_vol_get(void){
	//return ((g_usb_audio_status.speaker_mute << 7) | g_usb_audio_status.speaker_vol);
	return (speaker_setting.mute << 7) | (speaker_setting.vol_step & 0x7f);
}

u8 usbaud_mic_vol_get(void){
	//return ((g_usb_audio_status.speaker_mute << 7) | g_usb_audio_status.speaker_vol);
	return (mic_setting.mute << 7) | (mic_setting.vol_step & 0x7f);
}

void usbaud_mic_en(int en) {
	mic_setting.mute = en;
}

volatile int aaa_audio_intf_set = 0;
int usb_audio_class_out_intf_proc(u8 type, u8 feature_id){
	int ret = 0;
	usb_audio_status_t *p_aud = &g_usb_audio_status;

	aaa_audio_intf_set = 0;
	aaa_audio_intf_set = (type << 8);
	aaa_audio_intf_set |= feature_id;
	switch (type) {
		// mute control and sample
		case 0x01:
			switch (feature_id) {
			case 0x00://set sample frequency
				break;
			case AUDIO_FEATURE_ID_SPEAKER:// Feature ID 0x02, Speaker
				p_aud->speaker_mute = usbhw_read_ctrl_ep_data();
				p_aud->change |= 0x1;
				break;
			case AUDIO_FEATURE_ID_MIC:// Feature ID 0x05, MIC
				p_aud->mic_mute = usbhw_read_ctrl_ep_data();
				p_aud->change |= 0x2;
				break;
			default:
				ret = 1;
				break;
			}
			break;

		// volume control
		case 0x02:
			switch (feature_id) {
			case AUDIO_FEATURE_ID_SPEAKER: // Feature ID 0x02, Speaker
				p_aud->speaker_vol = usbhw_read_ctrl_ep_data() + (usbhw_read_ctrl_ep_data() << 8);
				p_aud->speaker_vol = ((p_aud->speaker_vol - (short)SPEAKER_VOL_MIN + SPEAKER_VOL_STEP) * (10 - 1)) >> 12;
				if (p_aud->speaker_vol < 0){
					p_aud->speaker_vol = 0;
				}
				p_aud->change |= 0x4;
				break;

			case AUDIO_FEATURE_ID_MIC: // Feature ID 0x05, MIC
				p_aud->mic_vol = usbhw_read_ctrl_ep_data() + (usbhw_read_ctrl_ep_data() << 8);
				p_aud->change |= 0x8;
				break;

			default:
				ret = 1;
				break;
			}

		default:
			break;
	}
	return ret;
}
		
// return -1 on fail,  0 on success
int usbaud_handle_set_speaker_cmd(int type) {
	if(type == AUDIO_FEATURE_MUTE){
		speaker_setting.mute = usbhw_read_ctrl_ep_data();
	}else if(type == AUDIO_FEATURE_VOLUME){
		u16 val = usbhw_read_ctrl_ep_u16();
		usbaud_set_speaker_vol(val);
	}else{
		return -1;
	}
	return 0;
}
// return -1 on fail,  0 on success
int usbaud_handle_set_mic_cmd(int type) {
	if(type == AUDIO_FEATURE_MUTE){
		mic_setting.mute = usbhw_read_ctrl_ep_data();
	}else if(type == AUDIO_FEATURE_VOLUME){
		u16 val = usbhw_read_ctrl_ep_u16();
		usbaud_set_mic_vol(val);
	}else{
	}
	return 0;
}

// return -1 on fail,  0 on success
int usbaud_handle_get_speaker_cmd(int req, int type) {
	if(type == AUDIO_FEATURE_MUTE){
		usbhw_write_ctrl_ep_data(speaker_setting.mute);
	}else if(type == AUDIO_FEATURE_VOLUME){
		switch (req) {
			case AUDIO_REQ_GetCurrent://Current volume
				usbhw_write_ctrl_ep_u16(speaker_setting.vol_cur);
				break;
			case AUDIO_REQ_GetMinimum://Minimum volumes
				usbhw_write_ctrl_ep_u16(SPEAKER_VOL_MIN);
				break;
			case AUDIO_REQ_GetMaximum://Maximum volume
				usbhw_write_ctrl_ep_u16(SPEAKER_VOL_MAX);
				break;
			case AUDIO_REQ_GetResolution://volume resolution
				usbhw_write_ctrl_ep_u16(SPEAKER_VOL_RES);
				break;
			default:
				return -1;
		}
	}else{
		return -1;
	}
	return 0;
}

// return -1 on fail,  0 on success
int usbaud_handle_get_mic_cmd(int req, int type) {
	if(type == AUDIO_FEATURE_MUTE){
		usbhw_write_ctrl_ep_data(mic_setting.mute);
	}else if(type == AUDIO_FEATURE_VOLUME){
		switch (req) {
			case AUDIO_REQ_GetCurrent:
				usbhw_write_ctrl_ep_u16(mic_setting.vol_cur);
				break;
			case AUDIO_REQ_GetMinimum:
				usbhw_write_ctrl_ep_u16(MIC_VOL_MIN);
				break;
			case AUDIO_REQ_GetMaximum:
				usbhw_write_ctrl_ep_u16(MIC_VOL_MAX);
				break;
			case AUDIO_REQ_GetResolution:
				usbhw_write_ctrl_ep_u16(MIC_VOL_RES);
				break;
			default:
				return -1;
		}
	}else{
		return -1;
	}
	return 0;
}
void usbaud_init(void) {
	if (USB_MIC_ENABLE && 1 == MIC_CHANNEL_COUNT) {
		usbaud_set_audio_mode(1);
	}
#if (USB_SPEAKER_ENABLE)
	usbaud_set_speaker_vol(SPEAKER_VOL_MAX);
#endif
#if (USB_MIC_ENABLE)
	mic_setting.vol_cur = MIC_VOL_DEF;
#endif
}
/**
 * @brief     This function serves to send data to USB.
 * @param[in] Input_Type - audio input type.
 * @param[in] Audio_Rate - audio rate. This value is matched with usb_desc.c :USB_RATE.
 * @return    none.
 */
void audio_tx_data_to_usb(AudioInput_Typedef Input_Type,AudioRate_Typedef Audio_Rate)
{

    int i=0;
    unsigned short data_h=0;
    unsigned short data_l=0;

    unsigned char length = 0;

    usbhw_reset_ep_ptr(USB_EDP_MIC);

    switch(Audio_Rate)
    {
    case 	AUDIO_8K:		length = 8;break;
    case	AUDIO_16K:		length = 16;break;
    case	AUDIO_32K:		length = 32;break;
//    case	AUDIO_48K:		length = 48;break;
    default:				length = 32;break;
    }
	if(Input_Type==DMIC)
    {
		for (i=0; i<length; i++) {
			if(reg_dfifo_irq_status & FLD_AUD_DFIFO0_L_IRQ)
				break;
			if(i%2)
			{
				data_h = reg_usb_mic_dat1;
				reg_usb_ep7_dat = data_h;
				reg_usb_ep7_dat = data_h>>8;
			}
			else{
				data_l = reg_usb_mic_dat0;
				reg_usb_ep7_dat = data_l;
				reg_usb_ep7_dat = data_l>>8;
			}
		}
    }
    else
    {
		for (i=0;i<length; i++)
		{
			if(reg_dfifo_irq_status & FLD_AUD_DFIFO0_L_IRQ)
			{
			    if(Input_Type==I2S_IN)
			    {
					data_l = reg_usb_mic_dat1;
					reg_usb_ep7_dat = data_l;
				    reg_usb_ep7_dat = data_l>>8;
			    }
			    else
			    {
				   if(i%2) {
					   reg_usb_ep7_dat = data_l;
					   reg_usb_ep7_dat = data_l>>8;
				   }
				   else{
					   reg_usb_ep7_dat = data_h;
					   reg_usb_ep7_dat = data_h>>8;
				   }
			    }
			}
			else
			{
			    if(Input_Type==I2S_IN)
			    {
				    data_l = reg_usb_mic_dat1;
					reg_usb_ep7_dat = data_l;
					reg_usb_ep7_dat = data_l>>8;
			    }
			    else
			    {
				   if(i%2)
				   {
					   data_l = reg_usb_mic_dat1;
					   reg_usb_ep7_dat = data_l;
					   reg_usb_ep7_dat = data_l>>8;
				   }
				   else
				   {
					   data_h = reg_usb_mic_dat0;
					   reg_usb_ep7_dat = data_h;
					   reg_usb_ep7_dat = data_h>>8;
				   }
			    }
			}
		}
    }
	reg_usb_ep7_ctrl = FLD_USB_EP_BUSY;//ACK iso in
}

/**
 * @brief     This function servers to set USB Input.
 * @param[in] none
 * @return    none.
 */

void audio_rx_data_from_usb(void)
{

	unsigned char len = reg_usb_ep6_ptr;
	usbhw_reset_ep_ptr(USB_EDP_SPEAKER);

	signed short data;

	for (int i=0; i<len; i+=2)
	{
		if(i%4)
		{
			data = reg_usb_ep6_dat & 0xff;
			data |= reg_usb_ep6_dat << 8;
			reg_usb_mic_dat0 = data;
		}
		else
		{
			data = reg_usb_ep6_dat & 0xff;
			data |= reg_usb_ep6_dat << 8;
			reg_usb_mic_dat1 = data;
		}
	}
	reg_usb_ep6_ctrl = FLD_USB_EP_BUSY;
}



/**
 * @brief		This function serves to send data to USB Host or receive data from USB Host
 * @param[in] 	none
 * @return 		none
 */
void usb_audio_irq_data_process(void)
{
	unsigned char irq = usbhw_get_eps_irq();
	if(irq & FLD_USB_EDP7_IRQ)
	{
		usbhw_clr_eps_irq(FLD_USB_EDP7_IRQ);
		usb_audio_mic_cnt++;
	}
	if(irq & FLD_USB_EDP6_IRQ)
	{
		usbhw_clr_eps_irq(FLD_USB_EDP6_IRQ);
		usb_audio_speaker_cnt++;
	}
}
